/*** version: 20051228-2113 ***/
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>

#include "tunnet.h"
#include "global.h"

extern char * neighbor_file;

/* lookup service id by name, e.g 
   int srvc=service_lookup("socks4")    
   after return, srvc=0x01;
*/ 
#define _TUNNET_C 1

LOOKUP_ITEM service_name[]={
	{ SRVC_TYPE_SOCKS4, "socks4"},
	{ SRVC_TYPE_SOCKS5, "socks5"},
	{ SRVC_TYPE_HTTP,   "http"},
	{ -1, NULL}
};
LOOKUP_ITEM address_type[]={
	{ ADDR_TYPE_IPV4, "ipv4"},
	{ ADDR_TYPE_IPV6, "ipv6"},
	{ -1, NULL}
};
LOOKUP_ITEM network_type[]={
	{ ADDR_TYPE_IPV4, "ipv4"},
	{ ADDR_TYPE_IPV6, "ipv6"},
	{ -1, NULL}
};

LOOKUP_ITEM auth_methods[]={
	{ METHOD_NONE, "none"},
	{ METHOD_PASS, "pass"},
	{ METHOD_X509, "x509"},
	{ -1, NULL}
};

LOOKUP_ITEM nbr_status[]={
	{ STATUS_UNKNOWN, "down"},
	{ STATUS_AUTHED, "authed"},
	{ STATUS_ACTIVE, "active"},
	{ -1, NULL}
};

struct hash_index route_cache[MAX_HASH];
pthread_mutex_t mutex_cache,mutex_neighbor,mutex_service, mutex_route;



int getUserHops(char *userNameHops,char *userName, unsigned char *hops)
{
	char *p, *token, *str_hop;

        p=strdup(userNameHops);
        token=strsep(&p, DELIMITER);
        if (token ==NULL){
                strcpy(userName,userNameHops);
                *hops=0;
                return 0;
        }
        else{
                strcpy(userName,token);
                //next token
                token=strsep(&p,DELIMITER);
                if(token ==NULL){
                        *hops=0;
                }
                else{
                        *hops=atoi(token);
                }
        }
        return 0;
}
int addUserHops(char *userNameHops, char *userName, int hops){
	char str_hop[2];
	strcpy(userNameHops, userName);
	strcat(userNameHops, DELIMITER);
	sprintf(str_hop, "%d",hops);
	strcat(userNameHops, str_hop);
	return 0;
}

int addrLength(unsigned char addrType)
{
    switch(addrType){
        case ADDR_TYPE_IPV4:
                return 4;
        break;
        case ADDR_TYPE_IPV6:
                return 16;
        break;
    }
}
int lookup_name(LOOKUP_ITEM *items, char * name){
	int i=0;
	while( items[i].num >0)	{
		if(! strcmp(name, items[i].str) ) 
			return items[i].num;
		i++;
	}
	return -1;
}
char * lookup_code(LOOKUP_ITEM *items, int code){
	int i=0;
	while( items[i].num >0)	{
		if(items[i].num==code) 
			return (char *)items[i].str;
		i++;
	}
	return (char *)items[i].str;
}
int methods_display(){
	int i=0;
	fprintf(stderr,"Available authication methods:\n");
	while ( auth_methods[i].num >0){
		fprintf(stderr,"%s(%d)\n",auth_methods[i].str,auth_methods[i].num);
		i++;
	}
}

unsigned short  hash_address(unsigned long dst_addr, unsigned short dst_port){
        unsigned long  high,low;
        unsigned short result;

        //high=dst_addr & 0xffff0000;
        high=dst_addr >>16 ;
        low=dst_addr & 0x0000ffff;
        //low=dst_addr ^ high;

        result=high ^ low;
        result=result ^ dst_port;
        result=result % MAX_HASH;
        return result;
}
void cache_initialize(struct hash_index *rc){
	int i;
	for(i=0; i<MAX_HASH; i++){
		rc[i].key=-1;
		rc[i].list=NULL;
	}
}

int cache_lookup(unsigned long dst_addr, unsigned short dst_port){
	int idx;
	struct hash_item * hp;
	idx=hash_address(dst_addr, dst_port);
	if(route_cache[idx].key ==-1) return -1;
	
	hp=route_cache[idx].list;
	while (hp){
		if( (dst_addr == hp->address) && (dst_port  ==hp->port)){
			return hp->nextHop;
		}
		else hp=hp->next;
	}
	return -1;
}
int cache_add(unsigned long dst_addr, unsigned short dst_port, int nodeID){
	unsigned short idx;
	struct hash_item * hp;
	time_t time_add;

	idx=hash_address(dst_addr, dst_port);

	pthread_mutex_lock(&mutex_cache);

	hp=(struct hash_item *)malloc(sizeof(struct hash_item));
	hp->address=dst_addr;
	hp->port=dst_port;
	hp->nextHop=nodeID;
	time(&time_add);
        hp->time_update=time_add;
	route_cache[idx].key=idx;
	hp->next=route_cache[idx].list;
	route_cache[idx].list=hp; 
	pthread_mutex_unlock(&mutex_cache);
}
int cache_update(unsigned long dst_addr, unsigned short dst_port, int nodeID){
	int idx;
        struct hash_item * hp;
	time_t time_now;

        idx=hash_address(dst_addr, dst_port);
        route_cache[idx].key =idx;
        hp=route_cache[idx].list;
	debug("cache_update: idx=%d \n", idx);
        while (hp){
            if( (dst_addr == hp->address) && (dst_port  ==hp->port)){
	 	time(&time_now);
		pthread_mutex_lock(&mutex_cache);
        	   hp->time_update=time_now;		
		   hp->nextHop=nodeID;
		pthread_mutex_unlock(&mutex_cache);
		return 0;
            }
            else hp=hp->next;
        }
}

struct access_list_item4 * new_acl4( in_addr_t netAddr, int prefix, int action){
       	unsigned long mask; 
	struct access_list_item4 * ap;

        ap=(struct access_list_item4*) malloc(sizeof(struct access_list_item4));

	if(ap==NULL) return  ap;

	ap->netAddress=netAddr;
	ap->prefix=prefix;
 	inet_pton(AF_INET, "255.255.255.255", &mask);
        mask =mask >> (32 - prefix);
	ap->mask=mask;
	ap->action=action;
	ap->next=NULL;
	return ap;
	
}

/* insert to the tail */
int acl_insert4(struct access_list4 *acl_p,struct access_list_item4 * item_p){

	struct access_list_item4 * tmp;

	if(acl_p==NULL)return -1;
	
	item_p->next=NULL;   // make sure it is NULL terminated  

	pthread_mutex_lock(&mutex_acl4);

	if(acl_p->first==NULL){
		acl_p->first=item_p;
	}
	else{
		tmp=acl_p->first;
		while(tmp->next) tmp=tmp->next;
		tmp->next=item_p;
	}
	acl_p->number ++;

	pthread_mutex_unlock(&mutex_acl4);
	return 0;
}
int acl_lookup4(struct access_list4 *acl, in_addr_t ip){

	struct access_list_item4 * tmp;

	if(acl ==NULL) return -1;
	tmp=acl->first;

	while(tmp){
            if( (ip & tmp->mask)  == (tmp->netAddress & tmp->mask) )
                     return tmp->action;
	    tmp=tmp->next;
	}
	return ACTION_DENY;
}
struct route_item *new_route(int netType, char * netAddr, int prefix, int hops, 
			     long bandwidth,int nbrID)
{
	
	struct route_item * rp;
	int length;

	rp=(struct route_item*) malloc(sizeof(struct route_item));
	rp->netType=netType;
	if(netType ==AF_INET) length=4;
	else  length=16;
	
	memcpy(rp->netAddress, netAddr,length);
	rp->prefix=prefix;
	rp->hops=hops; 
	
	rp->bandwidth=bandwidth;
	
	rp->nextHop=nbrID; 
        rp->next=NULL;
	
	return rp;
}

//insert the route item rp1 to the tail 
int route_insert(struct route * rt, struct route_item* rp1){
        struct route_item *rp;
	int netLen;

        if(!rt) return -1;

    pthread_mutex_lock(&mutex_route);
        //rp=rt->first;

        if(rt->first==NULL){
                rt->first=rp1;
        }
        else{
                rp=rt->first;

		netLen=addrLength(rp1->netType);
		if( !memcmp(rp->netAddress, rp1->netAddress, netLen)) goto leave;

                while(rp->next !=NULL) {
			rp =rp->next;
			if( !memcmp(rp->netAddress, rp1->netAddress, netLen)) {
				goto leave;
			}
		}
                rp->next=rp1;
        }
        rt->number++;

leave:
    pthread_mutex_unlock(&mutex_route);
	debug("route  leave mutex\n\n");
	debug("route insert end\n\n");
        return 0;
}
int route_display(struct route *rt){
        struct route_item *rp;
	char ipstr[128];

        if(!rt) return ;
        if(rt->number<=0) return;
        rp=rt->first;
        sys_log(info_log,"===========ROUTE  TABLE =======\n");
        sys_log(info_log,"%8s %-16s %-7s %-7s %-8s %-8s\n",
		"netType", "netAddress","prefix","hops","bandwidth","nexHop");
        while(rp){
		inet_ntop(rp->netType,rp->netAddress, ipstr, sizeof(ipstr));

        	sys_log(info_log,"%8d %-16s %-7d %-7d %-8ld %-8d\n",
			rp->netType,ipstr, rp->prefix, rp->hops, 
			rp->bandwidth,rp->nextHop); 
                rp=rp->next;
        }
	return 0;
}
        
int route_lookup(int addrType, unsigned long dst_addr, unsigned short dst_port){
        int idx,addrLength;
        struct route_item * rp;
	unsigned long mask, netAddr;

	if(addrType !=AF_INET) return -1; 

	addrLength=4;
        rp=routes.first;
        while (rp){
		if( addrType == rp->netType){ 
			inet_pton(AF_INET, "255.255.255.255", &mask);

			mask =mask >> (32 - rp->prefix);

			memcpy(&netAddr, rp->netAddress, addrLength);

			if( (netAddr & mask)  == (dst_addr & mask) )
				return rp->nextHop;
                }
                rp=rp->next; 
        }               
        return -1;
}      
//insert to the last of srvc 
int service_insert(struct service * srvc, struct service_item* sp1){
	struct service_item *sp;

        if(!srvc) return -1;

    pthread_mutex_lock(&mutex_service);
	sp=srvc->first;

	if(srvc->first==NULL){
		srvc->first=sp1;
	}	
	else{ 
		sp=srvc->first;
		while(sp->next !=NULL) sp =sp->next;
		sp->next=sp1;
	} 
	srvc->number++;
    pthread_mutex_unlock(&mutex_service);
	return 0;	
}
int service_get_status(struct service *srvc, unsigned char sType){
        struct service_item *sp;
                
        if(!srvc) return -1;
        if(srvc->number<=0) return -1;
        sp=srvc->first;
        while(sp){
                if(sp->srvcType == sType)
                        return sp->status;
                sp=sp->next;
        }
        return -1;
}      
int service_set_status(struct service *srvc, unsigned char sType,unsigned char status){
	struct service_item *sp;

        if(!srvc) return -1;
        if(srvc->number<=0) return -1;
        sp=srvc->first;
        while(sp){
		if(sp->srvcType == sType) {
			sp->status= status;
			return sp->status;
		}
                sp=sp->next;
        }
	return -1;
}
int service_display(struct service *srvc){
	struct service_item *sp;
	
	if(!srvc) return ;
	if(srvc->number<=0) return;
	sp=srvc->first;
	while(sp){
		sys_log(info_log,"\t\t>>>SERVICE(%-2d %-5d)\n",sp->srvcType,htons(sp->relayPort)); 	
		sp=sp->next;	
	}	
}
int service_update_nbr(int nodeID, unsigned char sType, unsigned short rPort){
	struct service *sp;
	struct service_item  *sip, *new_sp;
	struct node_item  *np;

	int found;

	found=0;
	np=(struct node_item *) neighbor_lookup_id(&neighbors,nodeID);

	sip=np->services.first;
	while(sip){
		if(sType == sip->srvcType){
			sip->relayPort=rPort;
			found=1;
		}
		sip=sip->next;
	}
	if(!found) {
		new_sp=(struct service_item *) malloc(sizeof(struct service_item));
		new_sp->srvcType=sType;
		new_sp->relayPort=rPort;
		new_sp->next=NULL;
		service_insert(&np->services,new_sp); 
	}
	return 0;
}
int service_delete(struct service *srvc){ // delete first item
	struct service_item *sp;
    pthread_mutex_lock(&mutex_service);
	sp=srvc->first;
	if(sp){
		srvc->first=sp->next;
		free(sp);
	}
	srvc->number--;
    pthread_mutex_unlock(&mutex_service);
	return srvc->number;
}
int service_clean(struct service *srvc){
	while(srvc->first) service_delete(srvc);
	return 0;
}

int neighbor_delete_byName(struct node* np, char * userName){
	struct node_item* nip,*tmp;
	int found=0;

	nip=neighbor_lookup_byName(np, userName);
	pthread_mutex_lock(&mutex_neighbor);

	if( nip == np->first){
		np->first=nip->next;
		free(nip);
		np->number--;
		found=1;
	}
	else{
		for( tmp=np->first; tmp !=NULL; tmp=tmp->next){
			if (tmp->next == nip) { 
				found =1; 
				break;
			}	
		}
		if(found){
			tmp->next=nip->next;
			free(nip);
			np->number--;
		}

	} 

}
int neighbor_delete(struct node *np){ // delete first item
	struct node_item *sp;
    pthread_mutex_lock(&mutex_neighbor);
	sp=np->first;
	if(sp){
		
		np->first=sp->next;
		service_clean(&sp->services);	
		free(sp);
	}
	np->number--;
    pthread_mutex_unlock(&mutex_neighbor);
	return np->number;
}
int neighbor_clean(struct node *np){
	while(np->first) neighbor_delete(np);
	return 0;
}



//insert to the tail of the 
int neighbor_insert(struct node * np,struct node_item * sp1) 
{
	struct node_item *sp;

    pthread_mutex_lock(&mutex_neighbor);

	sp1->nodeID=np->number;

	if(np->first==NULL){
		np->first=sp1;
	}
	else{ 
		sp=np->first;
		while(sp->next !=NULL) sp =sp->next;
                sp->next=sp1;		
	} 
	sp1->next=NULL;// cat it to the end

	np->number++;
    pthread_mutex_unlock(&mutex_neighbor);
	return np->number;
}
int neighbor_display(struct node *np){
	struct node_item *sp;
	struct in_addr ipaddr;
	char strip[64];

	sp=np->first;
	sys_log(info_log,"%-8s  %-30s %-8s  %-16s %-9s %-10s\n",
		"nodeID", "userName","addrType","ipAddress","ctrlPort","Source");
	while(sp){
	  //ipaddr.s_addr=ntohl(sp->ipAddress);
	  //inet_ntop(AF_INET, &ipaddr,strip, sizeof(strip));
          inet_ntop(AF_INET, &sp->ipAddress,strip, sizeof(strip));

	  sys_log(info_log,"%6d   %-30s  %-8d  %-16s %-8d %-4d\n",
		sp->nodeID, sp->userName, sp->addrType,
		strip,htons(sp->controlPort), sp->sourceNode); 	

	  if(sp->services.number){
		service_display(&sp->services);	
	  } 
	  sp=sp->next;	
	}	
}
/*********** save the neighbors information from memory to file *******/

int neighbor_dump(struct node *np){
	struct node_item *sp;
        struct in_addr ipaddr;
        char strip[64];
        FILE * f_nbr;
	char cmd[128];

	sprintf(cmd, "/bin/cp %s %s.old", neighbor_file,neighbor_file);
	system(cmd); 
	f_nbr=fopen(neighbor_file, "w");
	if(f_nbr==NULL){
        	sys_log(info_log,"neighbor_dump():Can not open file %s to write !\n", neighbor_file);
		return -1;
	}
        sp=np->first;
	
	fprintf(f_nbr,"################################################################\n");
	fprintf(f_nbr,"## This configure file will be rewrite by the running program  #\n");
        fprintf(f_nbr,"%-40s%-10s%-16s%-12s%-10s\n",
                "#userName","addrType","ipAddress","ctrlPort","Secret");
        while(sp){
	  //ipaddr.s_addr=ntohl(sp->ipAddress);
	  //inet_ntop(AF_INET, &ipaddr,strip, sizeof(strip));
          inet_ntop(AF_INET, &sp->ipAddress,strip, sizeof(strip));
          fprintf(f_nbr,"%-40s%-10s%-16s%-12d%",
               sp->userName, lookup_code(address_type,sp->addrType), strip, htons(sp->controlPort));

	  if (sp->sourceNode==0 && strlen(sp->secret)) 
	  {
		fprintf(f_nbr,"%-10s", sp->secret);
	  }
	  fprintf(f_nbr,"\n");

          sp=sp->next;
        }
	fclose(f_nbr);
}

struct node_item* neighbor_lookup_id(struct node *np, int nodeID)
{
	struct node_item *sp;
        sp=np->first;
        while(sp){	
		if(sp->nodeID == nodeID) 
			return sp;
		sp=sp->next;
	}
	return NULL; 
} 
int nodeIsUnderFirewall(struct node *np, int nodeID)
{
	struct node_item *sp;
        sp=np->first;
        while(sp){
           if(sp->nodeID == nodeID){
                  return sp->underFirewall;
	   }
           sp=sp->next;
        }
        return -1;  

}
int setNodeUnderFirewall(struct node *np, int nodeID,int value)
{
        struct node_item *sp;
        sp=np->first;
        while(sp){
           if(sp->nodeID == nodeID){
                  sp->underFirewall=value;
           }
           sp=sp->next;
        }
        return -1;

}
struct node_item* neighbor_lookup_byName(struct node * np, char * userName)
{
	
        struct node_item *sp;
        sp=np->first;
	//debug("UserName=%s", userName);
        while(sp){
                if(!strcmp(sp->userName, userName)) 
                        return sp;
                sp=sp->next;
        }
        return NULL;
}

struct node_item* neighbor_lookup_byIP(struct node * np, long ipAddress)
{

        struct node_item *sp;
        sp=np->first;
        //debug("UserName=%s", userName);
        while(sp){
                if(sp->ipAddress==ipAddress)
                        return sp;
                sp=sp->next;
        }
        return NULL;
}

struct node_item* neighbor_update(int nodeID, char * userName, unsigned int addrType,
	char * ipAddress , unsigned short controlPort)
{
	in_addr_t ipAddr;
        struct node_item *sp;

	memcpy((void*)&ipAddr,(void *) ipAddress, addrLength(addrType));
        sp=neighbors.first;
        while(sp){
                //if(!strcmp(sp->userName, userName))
                if(sp->nodeID==nodeID )
		{
    		   pthread_mutex_lock(&mutex_neighbor);
		   sp->addrType=addrType;
		   sp->ipAddress=ipAddr;
		   sp->controlPort=controlPort; 
    		   pthread_mutex_unlock(&mutex_neighbor);
                   return sp;
                }
		else sp=sp->next;
        }
        return NULL;
}
/********************************************************* 
  Try connnect (destAddr, destPort) via neighbor[nodeID]; 
   If success,  return 0, and *sock will be the connected socket ;
   Else,  return -1 , *sock  is undefined
*********************************************************/
int try_neighbor(int * sock,int nodeID,in_addr_t destAddr, unsigned short destPort,int hops){
     /* destAddr and destPort are in network address order */

	struct node_item *sp;
	struct service_item *srvc;
	struct sockaddr_in sai;
	int status;
	in_addr_t nbr_ip;
	unsigned short nbr_port;
	char * userName,userNameHops[MAX_USERNAME+2];
	struct in_addr tmp1,tmp2;
	char nip[64],dip[64];

	debug("try neighbor [%d]\n", nodeID);

       inet_ntop(AF_INET, &destAddr, dip, sizeof(dip));

	sp=neighbor_lookup_id(&neighbors, nodeID);
	if(!sp) return -1;
	if(sp->nodeID ==0) { // local 
		*sock = socket(AF_INET,SOCK_STREAM,0);
  		sai.sin_addr.s_addr = destAddr;
  		sai.sin_family = AF_INET;
  		sai.sin_port = destPort;
		
  		status=connect_nonb(*sock, (struct sockaddr*)&sai, sizeof(sai), &timeout);	
		return status;
        }
	else{
	    srvc=sp->services.first;	
	    
	    while(srvc){
		// try service , if success return 0; else return -1;
		switch(srvc->srvcType){
		   case  SRVC_TYPE_SOCKS4 :
			 nbr_ip=sp->ipAddress;
			 nbr_port=srvc->relayPort;
			 userName=neighbors.first->userName; //sp->userName;

			 inet_ntop(AF_INET,&sp->ipAddress,nip,sizeof(nip));
			 inet_ntop(AF_INET,&destAddr,dip,sizeof(dip));
			 debug("connect_socks4(%d, %s:%d %s %s:%d) \n",
				*sock,nip, ntohs(nbr_port), userName, 
				dip, ntohs(destPort) 
			 );
			 addUserHops(userNameHops, userName, hops+1);
			 status=connect_socks4(sock,nbr_ip,nbr_port,userNameHops,destAddr,destPort);
				/* Notice: all args are in network order */
			 /* if success, status ==0 ; else status=-1; */
			 return status;
		   break; 

		   case  SRVC_TYPE_SOCKS5 :
		   break; 
		} //switch	
		srvc=srvc->next;
	    }// while 
	}
	return -1;
}
/*********************************************************/
/*  Note: destAddr and destPort are both in network address order */
int p2p_connect(int *sock, in_addr_t srcAddr, in_addr_t destAddr, unsigned short destPort,int hops, int *nextNodeID) 
{
  struct sockaddr_in sai;
  int status;
  int index;
   struct node_item * np;
   struct in_addr tmp_ip;
   char str[64];
   //tmp_ip.s_addr=ntohl(destAddr);
   inet_ntop(AF_INET,&destAddr, str,sizeof(str)); 



    index=cache_lookup(destAddr, destPort);		

    if (index <0){ // not find in cache
	// try to find a path in the mandatory route table 
	index=route_lookup(AF_INET, destAddr,destPort);
	// if index ==0, the path is local route 
	debug("route lookup : index=%d \n", index);
    } 
    if(index >0){
	//if success , update cache, return 0; 
	
	status=try_neighbor(sock, index,destAddr,destPort,hops); // network address order	
	
	if(status==0){ // Success 
		cache_update(destAddr, destPort,index); 
		*nextNodeID=index;
		return 0;
	}	
    }


  // finally , try all neighbors 
 
    np=neighbors.first; 
    while(np){
	// try neighbor i , if success return 0;
	if(srcAddr == np->ipAddress) {
		debug("skiped neighbor[%d] \n", np->nodeID);
		np=np->next;
		status=-1;
		continue; // to avoid loop, skip the request neighbor
	}
	status=try_neighbor(sock, np->nodeID,destAddr, destPort,hops);	
	if(status==0){ // Success 
		//add to cache
		cache_add(destAddr, destPort, np->nodeID);	
		*nextNodeID=np->nodeID;
		return 0;
	}	
	np=np->next;
     }

    //all tried, return -1;
    *nextNodeID=-1;
    return status; /* -1 */

}

